﻿/* ----------------------------------------------------------------------------
 * Copyright (c) Philip Stears 
 *
 * This source code is subject to terms and conditions of the Microsoft Public 
 * License (Ms-PL).
 * 
 * By using this source code in any fashion, you are agreeing to be bound 
 * by the terms of the Microsoft Public License.
 * 
 * To request a copy of the Microsoft Public License, send a request by email
 * to license@connectbasic.net.
 *
 * This notice must not be removed from this software.
 * ---------------------------------------------------------------------------- */

namespace ConnectBasic.ObjectModel
{
    using ConnectBasic.Execution;
    using ConnectBasic.Execution.Binding;
    using DlrExpression = Microsoft.Linq.Expressions.Expression;

    /// <summary>
    /// Represents a for statement.
    /// </summary>
    public class ForStatement : StatementNodeBase
    {
        private string mVariableName;
        private ExpressionBase mStartExpression;
        private ExpressionBase mEndExpression;
        private ExpressionBase mStepExpression;
        private Block mStatements;

        /// <summary>
        /// Initializes a new instance of the <see cref="WhileStatement" /> class.
        /// </summary>
        /// <param name="variableName">The name of the variable which is used as the loop counter.</param>
        /// <param name="startExpression">The expression which determines the starting value of the loop counter.</param>
        /// <param name="endExpression">The expression which determines the ending value of the loop counter.</param>
        /// <param name="stepExpression">The expression which determines the step value added to the loop counter with each iteration. May be a null reference.</param>
        /// <param name="statements">The statements which get executed on each iteration of the loop.</param>
        public ForStatement(string variableName, ExpressionBase startExpression, ExpressionBase endExpression, ExpressionBase stepExpression, Block statements)
        {
            mVariableName = variableName;
            mStartExpression = startExpression;
            mEndExpression = endExpression;
            mStepExpression = stepExpression;
            mStatements = statements;
        }

        /// <summary>
        /// Gets the name of the counter variable.
        /// </summary>
        public string VariableName
        {
            get { return mVariableName; }
        }

        /// <summary>
        /// Gets the expression which determines the starting value of the loop counter. 
        /// </summary>
        public ExpressionBase StartExpression
        {
            get { return mStartExpression; }
        }
        /// <summary>
        /// Gets the expression which determines the end value of the loop counter.
        /// </summary>
        public ExpressionBase EndExpression
        {
            get { return mEndExpression; }
        }

        /// <summary>
        /// Gets the expression which determines the step value added to the loop counter with each iteration. May be a null reference.
        /// </summary>
        public ExpressionBase StepExpression
        {
            get { return mStepExpression; }
        }

        /// <summary>
        /// Gets the statements which get executed with each iteration of the loop.
        /// </summary>
        public Block Statements
        {
            get { return mStatements; }
        }

        /// <inheritdoc />
        public override DlrExpression Compile(BasicCodeGenerator gen)
        {

            // Resolve the loop variable
            var getLoopCounterValue = gen.MakeGetNameValueExpression(mVariableName);

            // Generate the test which determines whether the loop should continue
            var testExpression = GeneratorUtil.GenerateBinaryOperation(BinaryOperationKind.NotEqualTo, getLoopCounterValue, mEndExpression.Compile(gen));

            // Generate an expression which assigns the result of doing a binary add over the 
            // loop variable and, the result of the step expression - or if not specified, the 
            // value 1
            var add = (DlrExpression)null;

            if (mStepExpression == null)
            {
                if (getLoopCounterValue.Type == typeof(byte))
                {
                    add = GeneratorUtil.GenerateBinaryOperation(BinaryOperationKind.Add, getLoopCounterValue, DlrExpression.Constant((byte)1));
                }
                else if (getLoopCounterValue.Type == typeof(short))
                {
                    add = GeneratorUtil.GenerateBinaryOperation(BinaryOperationKind.Add, getLoopCounterValue, DlrExpression.Constant((short)1));
                }
                else if (getLoopCounterValue.Type == typeof(int))
                {
                    add = GeneratorUtil.GenerateBinaryOperation(BinaryOperationKind.Add, getLoopCounterValue, DlrExpression.Constant((int)1));
                }
                else
                {

                    // Unknown value type, so, use a short as the counter, and cast at runtime
                    add = GeneratorUtil.GenerateBinaryOperation(BinaryOperationKind.Add, getLoopCounterValue, DlrExpression.Constant((short)1));
                }
            }
            else
            {
                add = GeneratorUtil.GenerateBinaryOperation(BinaryOperationKind.Add, getLoopCounterValue, mStepExpression.Compile(gen));
            }

            var incrementExpression = gen.Scope.MakeSetValueExpression(
                mVariableName,
                gen.MeParameter,
                (type) => type == add.Type ? add : DlrExpression.Dynamic(new BasicConvertBinder(type), type, add));

            // Make sure we're setup in case one of the statements is an Exit Do
            var exitLabel = DlrExpression.Label();
            gen.PushFlowContext(BasicTokenKind.KeywordDo, exitLabel);
            var loopStatements = mStatements.Compile(gen);
            gen.PopFlowContext();

            // Generate the loop
            return Microsoft.Scripting.Ast.Utils.Loop(
                testExpression,
                incrementExpression,
                loopStatements,
                null,
                exitLabel,
                null
                ).Reduce();
        }
    }
}
